//HiSpeed Datastream
//Datastream simplified.


require("glon")

-- Split function for umsg splitting
function string.split(str,d)
	local t = {}
	local len = str:len()
	local i = 0
	while i*d < len do
		t[i+1] = str:sub(i*d+1,(i+1)*d)
		i=i+1
	end
	return t
end

-- Shared tables
DStream = {} 
DStream.MasterTable = {} 
DStream.DebugMode = true

-- String pooling for later use


if SERVER or CLIENT then 
	-- Always true, shows shared code
	
	function DStream.GetStreamById(DSGlobalID)
		local ReturnValue = DStream.MasterTable[DSGlobalID]
		if ReturnValue ~= nil then
			return ReturnValue
		else
			//DStream.Error("DStream.GetStreamById","ID " .. DSGlobalID .. " is not a valid stream") -- Throw an error
			return nil;
		end
	end
	
	function DStream.Error(ErrorName,ErrorDescription,HaltStack)
		-- Throw an error or
		local msg
		if HaltStack then
			msg = "HiSpeed DStream Error -> " .. ErrorName .. " threw a halting error ->"
		else
			msg = "HiSpeed DStream Error -> " .. ErrorName .. " threw a non-halting error ->"
		end
		local asterisks = ("*"):rep(math.max(msg:len(), ErrorDescription:len())+4).."\n"
		Msg(asterisks)
		Msg("  "..msg.."\n")
		Msg("  "..ErrorDescription .. "\n")
		Msg(asterisks)
		if HaltStack == true then
			Error("::DStreamHaltProcess::")
		end
	end
end
	function DStream.Debug(...)
		if (DStream.DebugMode == false) then return; end
		local Arguments = arg
		Msg ("Datastream Debug \"" .. Arguments[1] .. "\" ->\n")
		if type(Arguments[2]) == "table" then
			PrintTable(Arguments[2])
		else 
			for k,v in pairs(Arguments) do
				Msg (" -> " .. tostring(v) .. "\n")
			end
		end
		Msg ("<- Datastream Debug End \n")
	end
	
if SERVER then
	-- Server functions
	umsg.PoolString("DS_IncomingInfoPacket")
	
	function DStream.NewStream(DSPlayerSend,DSHandleID,DSContents,OnCompleted) -- Creates a new hispeed stream
		local NewStreamID = table.insert(DStream.MasterTable,{}) -- Insert into server's master table
		local NewStream = DStream.GetStreamById(NewStreamID) -- Get the new stream
		NewStream.Completed = false -- Is stream completed
		NewStream.SendToPlayer = DSPlayerSend
		local Orrig = glon.encode(DSContents)
		local EncodedContents = string.split(Orrig,128) -- Encoded contents with inf.loop protection off
		NewStream.ContentsEncoded = EncodedContents -- Set encoded
		NewStream.HandleID = DSHandleID 
		NewStream.GlobalID = NewStreamID
		NewStream.SizeOfStream = Orrig:len() * 2
		NewStream.BlockIndex = 1
		return NewStream; -- return:DStreamObject
	end
	function DStream.SendStream(DStreamObject)
		if DStreamObject.Completed ~= false then DStream.Error("DStream.SendStream","No valid unsent DStreamObject given",true) end
		DStreamObject.Stage = 0 -- Set the stage
		timer.Simple(0.10,DStream.SendInfoPacket,DStreamObject)
	end
	
	function DStream.SendInfoPacket(DStreamObject)
		umsg.Start("DS_IncomingInfoPacket",DStreamObject.SendToPlayer)
			umsg.Long(DStreamObject.GlobalID) -- Send the ID of the object
			umsg.String(DStreamObject.HandleID)
			umsg.Long(DStreamObject.SizeOfStream)
		umsg.End()
		DStreamObject.Stage = 1
	end
	
	function DStream.BeginSendingData(ply,cmd,args)
		local DS_GlobalID = args[1]
		local InputStream = DStream.GetStreamById(tonumber(args[1]))
		if ply:EntIndex() ~= InputStream.SendToPlayer:EntIndex() then DStream.Error("DStream.BeginSendingData","Player did not match ID of stream") return; end
		InputStream.Stage = 2
		timer.Simple(0.05,DStream.SendPacket,InputStream)
	end
	concommand.Add("____dsa",DStream.BeginSendingData)
	function DStream.ClientAcceptDataPacket(ply,cmd,args)
		local DS_GlobalID = args[1]
		local InputStream = DStream.GetStreamById(tonumber(args[1]))
		if ply:EntIndex() ~= InputStream.SendToPlayer:EntIndex() then DStream.Error("DStream.ClientAcceptDataPacket","Player did not match ID of stream") return; end
		timer.Simple(0.05,DStream.SendPacket,InputStream)
	end
	concommand.Add("____dsap",DStream.BeginSendingData)
	function DStream.SendPacket(DStreamObject)
		if DStreamObject.BlockIndex > #DStreamObject.ContentsEncoded then return; end
		local DataToSend = DStreamObject.ContentsEncoded[DStreamObject.BlockIndex]
		umsg.Start("DS_IncomingDataPacket",DStreamObject.SendToPlayer)
			umsg.Long(DStreamObject.GlobalID)
			umsg.String(DataToSend)
			if DStreamObject.BlockIndex == #DStreamObject.ContentsEncoded then
				umsg.Bool(true)
				DStream.MasterTable[DStreamObject.GlobalID] = nil; -- Free the mem
			else
				umsg.Bool(false)
			end
			DStream.Debug("SendPacket",DStreamObject.GlobalID,DataToSend)
		umsg.End()
		DStreamObject.BlockIndex = DStreamObject.BlockIndex+1
	end
	
	function DStream.TestFunction()
		local MyStream = DStream.NewStream(Entity(1),"MyTestSend",{"Q"})
		DStream.SendStream(MyStream)
	end
	
end


if CLIENT then
	-- Client Functions
	DStream.Hooks = {}
	DStream.Hooks.CanReceive = {}
	DStream.Hooks.OnDataReceived = {}
	DStream.Hooks.HookStream = {}
	function DStream.HookCanReceive(HandleID,UniqueID,Function)
		if DStream.Hooks.CanReceive[HandleID] == nil then
			DStream.Hooks.CanReceive[HandleID] = {}
		end
		DStream.Hooks.CanReceive[HandleID][UniqueID] = Function
	end
	function DStream.CallAllCanReceiveHooks(HandleID,SizeOfStream)
		local CanRun = true;
		if DStream.Hooks.CanReceive[HandleID] then
			for k,v in pairs(DStream.Hooks.CanReceive[HandleID]) do
				if DStream.Hooks.CanReceive[HandleID][k](SizeOfStream) == false then
					CanRun = false;
				end
			end
		end
		return CanRun;
	end
	function DStream.HookStream(HandleID,UniqueID,Function)
		if DStream.Hooks.HookStream[HandleID] == nil then
			DStream.Hooks.HookStream[HandleID] = {}
		end
		DStream.Hooks.HookStream[HandleID][UniqueID] = Function
	end
	function DStream.CallAllHookStreams(HandleID,Contents)
		local CanRun = true;
		if DStream.Hooks.HookStream[HandleID] then
			for k,v in pairs(DStream.Hooks.HookStream[HandleID]) do
				if DStream.Hooks.HookStream[HandleID][k](Contents) == false then
					CanRun = false;
				end
			end
		end
		return CanRun;
	end
	function DStream.IncomingInfoPacket(UMsg)
		local DS_GlobalID = UMsg:ReadLong()
		local DS_HandleID = UMsg:ReadString()
		local DS_SizeOfStream = UMsg:ReadLong()
		DStream.Debug("IncomingInfoPacket",DS_InfoPacketID,DS_GlobalID,DS_HandleID)
		if DStream.CallAllCanReceiveHooks(DS_HandleID,DS_SizeOfStream) == false then
			DStream.Debug("IncomingInfoPacket_Hook","The hook \"" .. DS_HandleID .. "\" was not received.")
		else
			DStream.HandleBeginningPacket({DS_GlobalID,DS_HandleID,DS_SizeOfStream})
		end
	end
	usermessage.Hook("DS_IncomingInfoPacket", DStream.IncomingInfoPacket)
	
	function DStream.AcceptStream(DStreamObject)
		timer.Simple(0.40,RunConsoleCommand,"____dsa",DStreamObject.GlobalID)
	end
	function DStream.AcceptDataPacket(DStreamObject)
		timer.Simple(0.20,RunConsoleCommand,"____dsap",DStreamObject.GlobalID)
	end
	function DStream.HandleDataPacket(msg)
		local GlobalID = msg:ReadLong()
		local DataPacket = msg:ReadString()
		local IsLastPacket = msg:ReadBool()
		DStream.Debug("HandleDataPacket",GlobalID,DataPacket,IsLastPacket)
		local InputStream = DStream.GetStreamById(GlobalID)
		if InputStream.ContentsEncoded == nil then
			InputStream.ContentsEncoded = ""
		end
		InputStream.ContentsEncoded = InputStream.ContentsEncoded .. DataPacket
		if IsLastPacket then
			InputStream.Completed = true
			DStream.CallAllHookStreams(InputStream.HandleID,glon.decode(InputStream.ContentsEncoded))
		else
			DStream.AcceptDataPacket(InputStream)
		end
		
	end
	usermessage.Hook("DS_IncomingDataPacket", DStream.HandleDataPacket)
	
	function DStream.HandleBeginningPacket(InputTable)
		local DS_GlobalID = InputTable[1]
		local DS_HandleID = InputTable[2]
		local DS_SizeOfStream = InputTable[3]
		DStream.MasterTable[DS_GlobalID] = {} -- Insert into server's master table
		local NewStreamID = DS_GlobalID
		local NewStream = DStream.GetStreamById(NewStreamID) -- Get the new stream
		NewStream.Completed = false -- Is stream completed
		NewStream.HandleID = DS_HandleID
		NewStream.GlobalID = DS_GlobalID
		NewStream.SizeOfStream = DS_SizeOfStream
		DStream.Debug("HandleBeginningPacket",NewStream)
		timer.Simple(0.25,DStream.AcceptStream,NewStream)
	end
	function DStream.TestFunction()
		DStream.HookCanReceive("MyTestSend","HookMyTestSend",function (SizeOfStream) Msg("SOS: " .. SizeOfStream .. "\n"); return true; end)
		DStream.HookStream("MyTestSend","HookMyTestSend",function (Contents) PrintTable(Contents); return true; end)
	end
end
